Skip to content

The separator a string field is escaped against is the one it is joined with - #499

Merged
erwan-joly merged 3 commits into
masterfrom
fix/serializer-escapes-field-separator
Aug 29, 2026
Merged

The separator a string field is escaped against is the one it is joined with#499
erwan-joly merged 3 commits into
masterfrom
fix/serializer-escapes-field-separator

Conversation

@erwan-joly

@erwan-joly erwan-joly commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Serializer-side fix for the class of bug NosCoreIO/NosCore#2336 works around in a caller.

The defect

A string field containing the field separator splits its own field in two and shifts every field after it. On master, a mate named Joyeux Mouton:

sc_p 1 333 0 15 0 0 0 0 ... 0 Joyeux Mouton 0
sc_n 1 333 0 15 0 0-1-1-1-1 ... Joyeux Mouton 0 0 -1 -1 -1 -1

The escaping already existed — aimed at the wrong separator

StringSerializer replaces the separator with ^ for any field that is not the last one. But the separator it is handed is PacketIndexAttribute.SpecialSeparator, which an ordinary property never declares, so IsNullOrEmpty short-circuits and the value goes out untouched. The real " " is applied afterwards by PacketSerializer and never reaches the substitution.

Measured on master with throwaway packets:

case wire escaped
string is the last index probe 1 Joyeux Mouton no
not last, no attribute probeb 1 Joyeux Mouton 2 no
not last, SpecialSeparator = " " probec 1 Joyeux^Mouton 2 yes

So the guard only ever fired for the handful of properties declaring a separator of their own — which is why SpecialSeparator = " " "fixes" it and why the whole thing looked like it worked.

ScnPacket.Name has carried /// Spaces should be replaced by "^" this whole time with nothing enforcing it.

The change

StringSerializer takes the separator the field is actually joined with, hoisting the existing splitter above the PropertySerializer call so it can be passed down. Deliberately unchanged:

  • the last field keeps its spaces — nothing follows it, and chat lines and miniland intros rely on that
  • a property with a SpecialSeparator still escapes that one, not the space
  • list elements keep today's behaviour; the escape argument is "" on that path, so this is not a blanket change

Tests

5 new. Two of them fail on master and pass here; the other three pin the behaviour that must not change (last field, SpecialSeparator) and pass either way — verified by stashing the fix and re-running.

Coverage includes the three real packets that carry a mate name — ScpPacket (index 31), ScnPacket (36), InPacket (1) — all non-last, all now escaped.

The 115 existing tests pin a lot of exact wire output. All still pass: 120/120.

Downstream

Once this ships, MateExtensions.DisplayName in NosCoreIO/NosCore#2336 becomes redundant.

The two manual escapes in NosCore — MinilandEntranceHandler and MlobjPacketHandlercannot be removed. MsgPacket.Message and MlintroPacket.Intro are both last-index fields, where the serializer deliberately does not escape; those are encoding the space for the client's own display, which is a different concern from field splitting.

Summary by CodeRabbit

  • Bug Fixes
    • Fixed packet serialization so spaces within string fields are preserved correctly.
    • Final fields now retain their spaces without being split unexpectedly.
    • Custom separators are escaped independently, improving handling of specialized packet formats.
    • Names containing spaces are now serialized correctly across supported packet types.
  • Tests
    • Added coverage for spaced fields, final-field behavior, custom separators, and packet names.

A string field that contained the separator split its own field in two and
shifted every field after it. Serialized on master, a mate named
"Joyeux Mouton" produced

    sc_p 1 333 0 15 ... 0 Joyeux Mouton 0

where the client reads a name and then a stray field.

The escaping was already there - StringSerializer replaces the separator with
"^" for any field that is not the last one - but the separator it was handed is
PacketIndexAttribute.SpecialSeparator, which an ordinary property never
declares. IsNullOrEmpty then short-circuited and the value went out untouched,
while the real " " separator was applied afterwards by PacketSerializer and
never reached the substitution. So the guard only ever fired for the handful of
properties that declare a separator of their own.

StringSerializer now takes the separator the field is actually joined with. The
last field still keeps its spaces, which chat lines and miniland intros rely on,
and a property with a SpecialSeparator still escapes that one rather than the
space.

ScnPacket.Name has carried "Spaces should be replaced by ^" as a doc comment
this whole time with nothing enforcing it.

The 115 existing tests pin a lot of exact wire output and all still pass.
@coderabbitai

coderabbitai Bot commented Aug 29, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

Next included review available in 32 minutes.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: eea876ed-a1cc-4557-b571-34bea3ee3387

📥 Commits

Reviewing files that changed from the base of the PR and between 8a83499 and 460a57e.

📒 Files selected for processing (7)
  • src/NosCore.Packets/Attributes/PacketIndexAttribute.cs
  • src/NosCore.Packets/NosCore.Packets.csproj
  • src/NosCore.Packets/Serializer.cs
  • src/NosCore.Packets/ServerPackets/Families/GInfoPacket.cs
  • src/NosCore.Packets/ServerPackets/Miniland/MlInfoBrPacket.cs
  • src/NosCore.Packets/ServerPackets/Miniland/MlintroPacket.cs
  • test/NosCore.Packets.Tests/StringFieldSeparatorTests.cs

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 6d1c0295-2fa6-4089-97af-85e290ff7879

📥 Commits

Reviewing files that changed from the base of the PR and between c8a3565 and 8a83499.

📒 Files selected for processing (2)
  • src/NosCore.Packets/Serializer.cs
  • test/NosCore.Packets.Tests/StringFieldSeparatorTests.cs

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


Walkthrough

StringSerializer now distinguishes field splitting from separator escaping. Call sites pass the relevant separator expressions, and tests cover spaces, custom separators, and packet name fields.

Changes

Field separator escaping

Layer / File(s) Summary
Serializer separator propagation
src/NosCore.Packets/Serializer.cs
StringSerializer replaces the configured escape separator with ^. PropertySerializer, ListSerializer, and PacketSerializer pass separate splitter and escape separator expressions.
Separator behavior validation
test/NosCore.Packets.Tests/StringFieldSeparatorTests.cs
Probe packets and serialization tests cover middle-field spaces, final-field spaces, dotted separators, and names in ScpPacket, ScnPacket, and InPacket.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to 8a834

The PR corrects delimiter escaping for non-final string fields while preserving final-field and custom-separator behavior, with focused tests covering the change. No actionable merge-blocking risk remains after normal checks and review.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.09% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 11 functions across 2 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: string fields now escape the separator used to join them.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/serializer-escapes-field-separator

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Two gaps left by escaping against the field separator alone.

A string inside a sub-packet only escaped its own separator. Its value sits in
a field of the packet above it, so a space still split that outer field:

    pinit 0 0|0|1|10|Joyeux Mouton|0|0|0|0|0|0

Nested strings now escape the outer separator as well.

The last field of a packet keeps its spaces, which is what chat lines need, but
a few fields carry text the client expects "^"-encoded wherever they sit -
mlintro, the miniland message on mlinfobr, the family message on ginfo. They
declare EscapeSpaces rather than every caller remembering to substitute.

122 tests pass.
@erwan-joly
erwan-joly merged commit 77ab304 into master Aug 29, 2026
3 checks passed
@erwan-joly
erwan-joly deleted the fix/serializer-escapes-field-separator branch August 29, 2026 23:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant